前面介紹了這麼多都還沒有說到使用者介面是長怎樣的,所以再來會先介紹。但因為有點長,所以會拆成兩篇。
class LangChainAgentApp(customtkinter.CTk):
def __init__(self):
super().__init__()
# 基本窗口設置
self.title("LangChain Agent 助手")
self.geometry("1200x700")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
# 設置主題
customtkinter.set_appearance_mode("dark")
customtkinter.set_default_color_theme("blue")
# 初始化變量
self.chat_history = []
self.message_queue = queue.Queue()
self.agent = None
self.system_tools = None
# 檔案處理相關變量
self.waiting_for_file_action = False
self.last_uploaded_file_content = ""
self.last_uploaded_filename = ""
self.last_analysis_result = "" # 保存最近的分析結果
# Word處理防重複標誌
self._word_opening = False
# 初始化 LangChain 組件
self._initialize_langchain()
# 建立GUI
self._create_gui()
# 開始訊息處理迴圈
self.check_queue()
# 歡迎訊息
self.message_queue.put(("系統", """🎯 LangChain Agent 助手已啟動!
我可以協助您:
📄 開啟應用程式 (記事本、小畫家、Word等)
🔧 檔案處理 (上傳、分析各種檔案格式)
內容生成 (創意寫作、心得感想等)
⚙️ 系統操作
請輸入問題或使用上傳檔案功能開始!"""))
customtkinter.CTk
是主視窗類別def _create_gui(self):
"""建立 GUI 界面"""
# 歷史紀錄顯示區 - 優化長內容顯示
self.history_textbox = customtkinter.CTkTextbox(
self,
state="disabled",
wrap="word",
scrollbar_button_color=("gray70", "gray30"),
scrollbar_button_hover_color=("gray60", "gray40")
)
self.history_textbox.grid(row=0, column=0, columnspan=2, padx=10, pady=(10, 5), sticky="nsew")
# 配置字體和顏色標籤
self._configure_text_tags()
# 底部輸入框架
self.input_frame = customtkinter.CTkFrame(self)
self.input_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 10), sticky="ew")
self.input_frame.grid_columnconfigure(0, weight=1)
# 輸入框
self.entry = customtkinter.CTkEntry(self.input_frame, placeholder_text="請在這裡輸入...")
self.entry.grid(row=0, column=0, padx=(10, 5), pady=5, sticky="ew")
self.entry.bind("<Return>", lambda event: self.start_agent_call())
# 送出按鈕
self.button = customtkinter.CTkButton(self.input_frame, text="送出問題", command=self.start_agent_call)
self.button.grid(row=0, column=1, padx=(0, 5), pady=5, sticky="e")
# 清除紀錄按鈕
self.clear_button = customtkinter.CTkButton(self.input_frame, text="清除紀錄", command=self.clear_history)
self.clear_button.grid(row=0, column=2, padx=(0, 5), pady=5, sticky="e")
# 上傳檔案按鈕
self.upload_button = customtkinter.CTkButton(self.input_frame, text="上傳檔案", command=self.upload_file)
self.upload_button.grid(row=0, column=3, padx=(0, 10), pady=5, sticky="e")
CTkTextbox
,設定為 disabled
狀態防止用戶編輯CTkFrame
包含所有輸入控制項columnspan=2
和 sticky="nsew"
確保自適應state="disabled"
:防止用戶直接編輯歷史記錄wrap="word"
:按單字換行,避免單字被截斷placeholder_text="請在這裡輸入..."
:提供用戶輸入提示bind("<Return>")
:綁定 Enter 鍵執行發送功能def _configure_text_tags(self):
"""配置文字標籤以支援markdown格式"""
# 取得底層的tkinter textbox
textbox = self.history_textbox._textbox
# 基本字體
base_font = font.Font(family="微軟正黑體", size=10)
# 配置不同的文字樣式
textbox.tag_configure("speaker_user", foreground="#4A9EFF", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
textbox.tag_configure("speaker_agent", foreground="#00D26A", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
textbox.tag_configure("speaker_system", foreground="#FF6B35", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
textbox.tag_configure("speaker_analysis", foreground="#9B59B6", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
# Markdown樣式
textbox.tag_configure("heading1", foreground="#FFD700", font=font.Font(family="微軟正黑體", size=16, weight="bold"))
textbox.tag_configure("heading2", foreground="#FFA500", font=font.Font(family="微軟正黑體", size=14, weight="bold"))
textbox.tag_configure("heading3", foreground="#FF6347", font=font.Font(family="微軟正黑體", size=12, weight="bold"))
textbox.tag_configure("bold", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
textbox.tag_configure("italic", font=font.Font(family="微軟正黑體", size=10, slant="italic"))
textbox.tag_configure("code", background="#2B2B2B", foreground="#F8F8F2", font=font.Font(family="Consolas", size=9))
textbox.tag_configure("code_block", background="#1E1E1E", foreground="#D4D4D4", font=font.Font(family="Consolas", size=9), relief="solid", borderwidth=1)
textbox.tag_configure("quote", foreground="#B0B0B0", font=font.Font(family="微軟正黑體", size=10, slant="italic"), lmargin1=20, rmargin=20)
textbox.tag_configure("list_item", lmargin1=20, lmargin2=30)
textbox.tag_configure("link", foreground="#4A9EFF", underline=True)
textbox.tag_configure("emphasis", foreground="#FFD700", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
code
:行內程式碼,深灰背景code_block
:程式碼區塊,黑色背景加邊框def start_agent_call(self):
"""啟動 Agent 處理用戶輸入"""
question = self.entry.get().strip()
if question:
self.entry.delete(0, "end")
self._append_to_history("使用者", question)
self.message_queue.put(("Agent", "正在處理…"))
# 啟動新執行緒來呼叫 Agent
thread = threading.Thread(target=self.get_agent_response, args=(question,))
thread.start()
else:
self.message_queue.put(("系統", "請輸入一個問題。"))
def clear_history(self):
"""清除對話歷史"""
self.chat_history.clear()
self._update_history_display()
# 重新初始化 LangChain 組件
self._initialize_langchain()
self.message_queue.put(("系統", "對話紀錄已清除。"))
def check_queue(self):
"""檢查訊息佇列"""
try:
processed_count = 0
max_process_per_cycle = 5 # 限制每次處理的最大訊息數
while processed_count < max_process_per_cycle:
speaker, message = self.message_queue.get_nowait()
if speaker == "remove_waiting":
self._remove_waiting_message()
else:
self._append_to_history(speaker, message)
processed_count += 1
except queue.Empty:
pass
except Exception as e:
print(f"[DEBUG] 佇列處理錯誤: {e}")
# 增加檢查間隔,降低CPU使用率
self.after(200, self.check_queue)